package com.cui.project.service.data;


import com.api.common.model.entity.GatewayAccessLog;
import com.api.common.model.entity.User;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cui.project.api.PythonForecastClient;
import com.cui.project.mapper.InterfaceTrafficForecastMapper;
import com.cui.project.mapper.InterfaceTrafficMetricsHourlyMapper;
import com.cui.project.mapper.InterfaceTrafficMetricsMapper;
import com.cui.project.mapper.UserInterfaceTrafficMetricsMapper;
import com.cui.project.model.entity.InterfaceTrafficForecast;
import com.cui.project.model.entity.InterfaceTrafficMetrics;
import com.cui.project.model.entity.UserInterfaceTrafficForecast;
import com.cui.project.model.entity.UserInterfaceTrafficMetrics;
import com.cui.project.model.enums.ForecastTypeEnum;
import com.cui.project.service.InterfaceTrafficForecastService;
import com.cui.project.service.UserInterfaceTrafficForecastService;
import com.cui.project.service.UserInterfaceTrafficMetricsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

/**
 * Python时间序列流量预测服务
 * 
 * 核心功能：
 * 1. 定时从数据库获取接口的历史流量数据
 * 2. 调用Python服务进行时间序列预测
 * 3. 将预测结果存储到数据库，供资源调度服务使用
 * 
 * 技术架构：
 * - Java层：负责数据准备、调度、存储
 * - Python层：负责复杂的时间序列算法（Prophet、LSTM、ARIMA等）
 * - 跨语言通信：通过HTTP REST API
 * 
 * 预测模型：
 * - Prophet: Facebook开源，适合有明显周期性的数据
 * - LSTM: 长短期记忆网络，适合复杂非线性模式
 * - ARIMA: 经典统计模型，适合平稳时间序列
 * - Ensemble: 集成多个模型，提高预测准确度
 * 
 * 数据要求：
 * - 最少168小时（7天）的历史数据
 * - 数据完整性：无大量缺失值
 * - 数据质量：无异常突刺
 * 
 * @author cui
 * @date 2025-11-02
 */
@Service
@Slf4j
public class PythonTimeSeriesForecastService {

    /**
     * 流量指标数据访问对象（分钟级）
     * 用于查询历史流量数据
     */
    @Resource
    private InterfaceTrafficMetricsMapper metricsMapper;

    /**
     * 流量指标数据访问对象（小时级）
     * 小时级数据更适合长期预测
     */
    @Resource
    private InterfaceTrafficMetricsHourlyMapper metricsHourlyMapper;

    /**
     * 流量预测服务
     * 用于保存预测结果
     */
    @Resource
    private InterfaceTrafficForecastService forecastService;

    /**
     * Python预测服务客户端
     * 封装了与Python服务的HTTP通信
     */
    @Resource
    private PythonForecastClient pythonForecastClient;



    /**
     * 流量指标数据访问对象（分钟级）
     * 用于查询历史流量数据
     */
    @Resource
    private UserInterfaceTrafficMetricsMapper userInterfaceTrafficMetricsMapper;

    /**
     * 流量预测服务
     * 用于保存预测结果
     */
    @Resource
    private UserInterfaceTrafficForecastService userInterfaceTrafficForecastService;


    @Resource(name = "ioExecutor")
    private Executor executor;
    
    
    @Resource
    private ApplicationContext applicationContext;


    /**
     * 最小预测数据量要求：168小时（7天）
     * 时间序列预测需要足够的历史数据才能捕捉到周期性规律
     * 7天数据可以捕捉到：
     * - 工作日vs周末的差异
     * - 每日的高峰时段规律
     */
    private static final int MIN_FORECAST_SIZE=168;

    /**
     * 默认置信水平：0.95（95%置信区间）
     * 表示预测的置信区间能覆盖95%的实际情况
     */
    private static final double DEFAULT_CONFIDENCE_LEVEL=0.95;

    /**
     * 日期时间格式化工具
     * 用于与Python服务的时间格式统一
     */
    private static final DateTimeFormatter formatter =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 临时保存列表的初始容量：100
     * 用于批量保存预测结果，减少数据库操作次数
     */
    private static final int DEFAULT_TMP_SAVE_LIST_SIZE=1000;
    
    /**
     * 最大批量保存大小：300
     * 超过此大小时触发一次批量保存，避免内存占用过大
     */
    private static final int MAX_BATCH_SIZE=500;

    /**
     * 使用Python服务进行流量预测（定时任务）
     * 
     * 执行频率：每小时第10分钟执行（如10:10, 11:10, 12:10...）
     * 执行时间选择原因：
     * - 避开整点时刻，降低系统负载
     * - 在流量聚合任务（每小时第5分钟）完成后执行
     * - 为资源调度任务（每30分钟）提供最新预测数据
     * 
     * 业务流程：
     * 1. 查询所有活跃接口列表
     * 2. 逐个接口获取过去7天的历史流量数据
     * 3. 调用Python服务进行时间序列预测
     * 4. 解析预测结果并批量保存到数据库
     * 
     * 性能优化：
     * - 批量保存：每300条记录保存一次，减少数据库交互
     * - 异常隔离：单个接口预测失败不影响其他接口
     * - 数据过滤：历史数据不足的接口自动跳过
     * 
     * 预测时长：未来24小时（1天）
     * 预测模型：默认使用Prophet模型
     */
    @Scheduled(cron = "0 10 * * * ?")
//    @Scheduled(cron = "0 * * * * ?")
    public void forecastTrafficWithPython(){
        log.info("开始执行Python时间序列流量预测");
        LocalDateTime now=LocalDateTime.now();
        // 设置时间范围：过去7天
        LocalDateTime startTime = now.minus(7, ChronoUnit.DAYS).truncatedTo(ChronoUnit.HOURS);
        LocalDateTime endTime = now.truncatedTo(ChronoUnit.HOURS);
        try {
            CompletableFuture<Void> interfaceForecastFuture = CompletableFuture.runAsync(() -> {
                try {
                    forecastInterfaceTraffic(startTime, endTime);
                } catch (Exception e) {
                    log.error("接口级流量预测任务失败", e);
                }
            }, executor);
            CompletableFuture<Void> userInterfaceForecastFuture = CompletableFuture.runAsync(() -> {
                try {
                    forecastUserInterfaceTraffic(startTime, endTime);
                } catch (Exception e) {
                    log.error("用户接口级流量预测任务失败", e);
                }
            }, executor);

            // 等待两个预测任务都完成
            CompletableFuture.allOf(interfaceForecastFuture, userInterfaceForecastFuture).join();
            log.info("本轮流量预测任务全部完成");

        }catch (Exception e){
            // 整个预测任务失败
            // 可能的原因：数据库连接失败、Python服务不可用等
            log.error("Python时间序列流量预测失败", e);
        }
    }


    public void forecastUserInterfaceTraffic(LocalDateTime startTime, LocalDateTime endTime) {
        log.info("开始执行用户接口流量预测");
        PythonTimeSeriesForecastService proxy = applicationContext.getBean(PythonTimeSeriesForecastService.class);
        // 步骤1：获取前7天所有用户的接口流量活动的用接口ID列表
        // 活跃接口：近期用户有访问记录的接口
        List<Map<String,Object>> activeUserInterfaceIds = userInterfaceTrafficMetricsMapper.selectActiveUserInterfaces(startTime,endTime);
        if(activeUserInterfaceIds.isEmpty()){
            log.info("无用户接口流量活动数据，{} - {}",startTime,endTime);
            return;
        }

        Map<Long, List<Long>> userInterfaceMap = activeUserInterfaceIds.stream()
                .collect(Collectors.groupingBy(
                        m -> ((Number) m.get("user_id")).longValue(),
                        Collectors.mapping(m -> ((Number) m.get("interface_info_id")).longValue(), Collectors.toList())
                ));
        // 创建临时列表，用于批量保存预测结果
        // 初始容量1000，避免频繁扩容
        List<UserInterfaceTrafficForecast> tmpSaveList=new ArrayList<>(DEFAULT_TMP_SAVE_LIST_SIZE);


        // 步骤2：遍历每个用户使用活跃接口，分别进行预测
        for (Map.Entry<Long, List<Long>> entry : userInterfaceMap.entrySet()) {
            Long userId = entry.getKey();
            List<Long> interfaceIds = entry.getValue();

            // 步骤2.1：获取该用户使用接口过去7天的历史流量数据
            // 注意：这里实际只查询了1天的数据，可能需要修改为7天
            LambdaQueryWrapper<UserInterfaceTrafficMetrics> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UserInterfaceTrafficMetrics::getUserId,userId);
            queryWrapper.between(UserInterfaceTrafficMetrics::getTimeBucket,
                    Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()),
                    Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant())
                    );
            // 只查询需要的字段，减少数据传输量
            queryWrapper.select(UserInterfaceTrafficMetrics::getInterfaceInfoId,UserInterfaceTrafficMetrics::getRequestCount,UserInterfaceTrafficMetrics::getTimeBucket);
            List<UserInterfaceTrafficMetrics> historicalData = userInterfaceTrafficMetricsMapper.selectList(queryWrapper);
            if(historicalData.isEmpty()){
                log.info("无用户接口数据");
            }

            Map<Long, List<UserInterfaceTrafficMetrics>> uitmMap = historicalData.stream()
                    .collect(Collectors.groupingBy(UserInterfaceTrafficMetrics::getInterfaceInfoId));

            int total=0;

            //遍历当前用户过去7天使用过的接口id
            for (Long interfaceId : interfaceIds) {
                List<UserInterfaceTrafficMetrics> trafficMetrics = uitmMap.get(interfaceId);
                if(trafficMetrics==null||trafficMetrics.isEmpty())continue;
                try {
                    // 步骤2.2：数据质量检查
                    // 如果历史数据量不足168小时（7天），跳过该接口
                    // 数据量不足会导致预测不准确
                    if(trafficMetrics.size()<MIN_FORECAST_SIZE){
                        log.warn("用户 {} 接口 {} 历史数据不足，跳过预测，size={}", userId,interfaceId,trafficMetrics.size());
                        continue;
                    }
                    // 步骤3：构造Python服务的预测请求
                    PythonForecastClient.ForecastRequest request = buildForecastRequest(interfaceId, trafficMetrics);
                    // 步骤4：调用Python服务进行预测
                    // 发送HTTP请求到Python Flask服务
                    PythonForecastClient.ForecastResponse response = pythonForecastClient.forecast(request);

                    // 步骤5：保存预测结果到数据库
                    // 将Python返回的预测数据点转换为实体对象
                    for (PythonForecastClient.PredictionPoint prediction : response.getPredictions()) {
                        // 构造预测结果实体
                        UserInterfaceTrafficForecast forecast = new UserInterfaceTrafficForecast();
                        forecast.setInterfaceInfoId(interfaceId);
                        Instant instant = LocalDateTime.parse(prediction.getTimestamp(), formatter).atZone(ZoneId.systemDefault()).toInstant();
                        forecast.setForecastTime(Date.from(instant)); // 预测时间点
                        forecast.setForecastRequestCount(prediction.getPredictedValue()); // 预测请求数
                        forecast.setConfidenceLower(prediction.getConfidenceLower()); // 置信区间下界
                        forecast.setConfidenceUpper(prediction.getConfidenceUpper()); // 置信区间上界
                        forecast.setModelType(response.getModelType()); // 模型类型
                        forecast.setModelVersion("python-v1.0"); // 模型版本

                        // 从准确度指标中提取评分
                        // accuracyMetrics可能包含：MAE、RMSE、MAPE、R2等指标
                        if (response.getAccuracyMetrics() != null) {
                            Object accuracyScore = response.getAccuracyMetrics().get("accuracy_score");
                            if (accuracyScore != null) {
                                forecast.setAccuracyScore(new BigDecimal(accuracyScore.toString()));
                            }
                        }
                        // 批量保存优化：当临时列表达到最大容量时，批量写入数据库
                        // 减少数据库交互次数，提高性能
                        if(tmpSaveList.size()>=MAX_BATCH_SIZE){
                            proxy.saveUserForecastBatchWithTransaction(tmpSaveList);
                            tmpSaveList.clear(); // 清空列表，继续收集
                        }
                        tmpSaveList.add(forecast);
                    }
                    total+=response.getPredictions().size();
                    log.debug("用户 {} 接口 {} 预测完成，生成 {} 个预测数据点",
                            userId,interfaceId, response.getPredictions().size());
                }catch (Exception e){
                    // 单个用户接口预测失败，记录日志但不影响其他接口
                    // 异常隔离：确保一个接口的问题不会导致整个预测任务失败
                    log.error("用户 {} 接口 {} 预测失败", userId,interfaceId, e);
                }
            }
            log.info("用户 {} 预测完成，总生成 {} 个预测数据点",
                    userId, total);
        }

        // 每个接口预测完成后，保存该接口的预测结果
        // 避免数据积压在内存中
        if(!tmpSaveList.isEmpty()){
            proxy.saveUserForecastBatchWithTransaction(tmpSaveList);
            tmpSaveList.clear();
        }
    }



    public void forecastInterfaceTraffic(LocalDateTime startTime, LocalDateTime endTime) {
        log.info("开始执行接口流量预测");
        PythonTimeSeriesForecastService proxy = applicationContext.getBean(PythonTimeSeriesForecastService.class);

        // 步骤1：获取所有有流量活动的接口ID列表
        // 活跃接口：近期有访问记录的接口
        List<Long> activeInterfaceIds = metricsMapper.selectActiveInterfaceIds(startTime,endTime);
        if(activeInterfaceIds.isEmpty()){
            log.warn("无接口流量活动数据，{} - {}",startTime,endTime);
            return;
        }

        // 创建临时列表，用于批量保存预测结果
        // 初始容量1000，避免频繁扩容
        List<InterfaceTrafficForecast> tmpSaveList=new ArrayList<>(DEFAULT_TMP_SAVE_LIST_SIZE);

        // 步骤2：遍历每个活跃接口，分别进行预测
        for (Long interfaceId : activeInterfaceIds) {
            try {
                // 步骤2.1：获取该接口过去7天的历史流量数据
                // 注意：这里实际只查询了1天的数据，可能需要修改为7天
                LambdaQueryWrapper<InterfaceTrafficMetrics> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(InterfaceTrafficMetrics::getInterfaceInfoId,interfaceId);
                queryWrapper.between(InterfaceTrafficMetrics::getTimeBucket,
                        Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()),
                        Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()));
                // 只查询需要的字段，减少数据传输量
                queryWrapper.select(InterfaceTrafficMetrics::getInterfaceInfoId,
                        InterfaceTrafficMetrics::getRequestCount,
                        InterfaceTrafficMetrics::getTimeBucket
                        );
                List<InterfaceTrafficMetrics> historicalData = metricsMapper.selectList(queryWrapper);

                // 步骤2.2：数据质量检查
                // 如果历史数据量不足168小时（7天），跳过该接口
                // 数据量不足会导致预测不准确
                if(historicalData.size()<MIN_FORECAST_SIZE){
                    log.warn("接口 {} 历史数据不足，跳过预测，size={}", interfaceId,historicalData.size());
                    continue;
                }

                // 步骤3：构造Python服务的预测请求
                PythonForecastClient.ForecastRequest request = buildForecastRequest(interfaceId, historicalData);

                // 步骤4：调用Python服务进行预测
                // 发送HTTP请求到Python Flask服务
                PythonForecastClient.ForecastResponse response = pythonForecastClient.forecast(request);

                // 步骤5：保存预测结果到数据库
                // 将Python返回的预测数据点转换为实体对象
                for (PythonForecastClient.PredictionPoint prediction : response.getPredictions()) {
                    // 构造预测结果实体
                    InterfaceTrafficForecast forecast = new InterfaceTrafficForecast();
                    forecast.setInterfaceInfoId(interfaceId);
                    Instant instant = LocalDateTime.parse(prediction.getTimestamp(), formatter).atZone(ZoneId.systemDefault()).toInstant();
                    forecast.setForecastTime(Date.from(instant)); // 预测时间点
                    forecast.setForecastRequestCount(prediction.getPredictedValue()); // 预测请求数
                    forecast.setConfidenceLower(prediction.getConfidenceLower()); // 置信区间下界
                    forecast.setConfidenceUpper(prediction.getConfidenceUpper()); // 置信区间上界
                    forecast.setModelType(response.getModelType()); // 模型类型
                    forecast.setModelVersion("python-v1.0"); // 模型版本

                    // 从准确度指标中提取评分
                    // accuracyMetrics可能包含：MAE、RMSE、MAPE、R2等指标
                    if (response.getAccuracyMetrics() != null) {
                        Object accuracyScore = response.getAccuracyMetrics().get("accuracy_score");
                        if (accuracyScore != null) {
                            forecast.setAccuracyScore(new BigDecimal(accuracyScore.toString()));
                        }
                    }

                    // 批量保存优化：当临时列表达到最大容量时，批量写入数据库
                    // 减少数据库交互次数，提高性能
                    if(tmpSaveList.size()>=MAX_BATCH_SIZE){
                        proxy.saveForecastBatchWithTransaction(tmpSaveList);
                        tmpSaveList.clear(); // 清空列表，继续收集
                    }
                    tmpSaveList.add(forecast);
                }

                // 每个接口预测完成后，保存该接口的预测结果
                // 避免数据积压在内存中
                if(!tmpSaveList.isEmpty()){
                    proxy.saveForecastBatchWithTransaction(tmpSaveList);
                    tmpSaveList.clear();
                }

                log.info("接口 {} 预测完成，生成 {} 个预测数据点",
                        interfaceId, response.getPredictions().size());

            }catch (Exception e){
                // 单个接口预测失败，记录日志但不影响其他接口
                // 异常隔离：确保一个接口的问题不会导致整个预测任务失败
                log.error("接口 {} 预测失败", interfaceId, e);
            }
        }
    }

    /**
     * ✅ 批量保存接口流量数据（带事务）
     * 必须是 public 方法，才能被Spring代理
     */
    public void saveForecastBatchWithTransaction(List<InterfaceTrafficForecast> batch) {
        forecastService.saveBatch(batch);
        log.debug("批次保存成功，数量：{}", batch.size());
    }

    public void saveUserForecastBatchWithTransaction(List<UserInterfaceTrafficForecast> batch) {
        userInterfaceTrafficForecastService.saveBatch(batch);
        log.debug("批次保存成功，数量：{}", batch.size());
    }

    private <T> PythonForecastClient.ForecastRequest buildForecastRequest(Long interfaceId, List<T> historicalData) {
        PythonForecastClient.ForecastRequest request = new PythonForecastClient.ForecastRequest();
        request.setInterfaceId(interfaceId);
        request.setForecastHours(24); // 预测未来24小时
        request.setModelType(ForecastTypeEnum.PROPHET.getText()); // 使用Prophet模型
        request.setConfidenceLevel(DEFAULT_CONFIDENCE_LEVEL); // 95%置信区间

        // 步骤3.1：转换历史数据格式
        // 从实体对象转换为Map格式，符合Python服务的接口要求
        List<Map<String, Object>> histDataList = historicalData.stream()
                .map(m -> {
                    Map<String, Object> map = new HashMap<>();
                    if(m instanceof InterfaceTrafficMetrics){
                        InterfaceTrafficMetrics it=(InterfaceTrafficMetrics) m;
                        map.put("timestamp", LocalDateTime.ofInstant(it.getTimeBucket().toInstant(),ZoneId.systemDefault()).format(formatter));
                        map.put("request_count", it.getRequestCount());
                    }else{
                        UserInterfaceTrafficMetrics uit=(UserInterfaceTrafficMetrics) m;
                        map.put("timestamp", LocalDateTime.ofInstant(uit.getTimeBucket().toInstant(),ZoneId.systemDefault()).format(formatter));
                        map.put("request_count", uit.getRequestCount());
                    }
                    return map;
                }).collect(Collectors.toList());

        request.setHistoricalData(histDataList);
        return request;
    }
}
